import 'dart:ffi';
import 'dart:io';
import 'dart:math';

import 'base01-语言概况.dart';

class Person {
  String name = "";
  int age = 10;
}

class Employee extends Person {
  String address = "测试地址";
}

/*

    https://dart.cn/guides/language/language-tour#return-values
 */
void main() {
  var employee = Employee();
  employee.name = "worker";
  employee.age = 20;

  var tempNamge = (employee as Person).name;
  print('tempNamge = $tempNamge');

  const e1 = "";

  if (employee is Person) {
    print('age = ${employee.age}');
  }

  if (e1 is! Employee) {
    print('不是雇佣者');
  }

  /*
      赋值运算符

      1.??=
        //原来的变量有值时候，那么??=不执行
        //原来的变量为null，则赋值给这个变量

      2.??:
        //??前面的数据有值，就使用??前面的数据
        //??前面的数据为null，就是用后面的值

       可以使用 = 来赋值，同时也可以使用 ??= 来为值为 null 的变量赋值。
       https://www.jianshu.com/p/a7efca3d4a1c

   */

  print("------------------------------------------------------------------");

  var a = null;
  a ??= 2;

  var b = 1;
  b ??= 3; //这里不会执行
  //58:3: Warning: Operand of null-aware operation '??=' has type 'int' which excludes null.
  //   b ??= 3;
  //   ^

  /*
      按位和移位运算符
      在 Dart 中，二进制位运算符可以操作二进制的某一位，但仅适用于整数。

      运算符	描述
      &	按位与
      |	按位或
      ^	按位异或
      ~表达式	按位取反（即将 “0” 变为 “1”，“1” 变为 “0”）
      <<	位左移
      >>	位右移
      >>>	无符号右移

      注意：
      >>> 操作符在 2.14 以上的 Dart 版本 中可用。
   */

  var value = 0x22;
  var bitmask = 0x0f;

  print((value & bitmask) == 0x02);
  print((value << 4));

  print("------------------------------------------------------------------");

  //如果赋值是根据判定是否为 null 则考虑使用 ??。
  print('playerName = ${playerName(null)}');

  var model1 = Model();
  model1.hello('word');

  var model2 = Model()
    ..hello('world')
    ..sayHi('bill')
    ..sayGoodbye('bill');

  //For 循环
  // 可以使用标准的 for 循环进行迭代。例如：
  var message = StringBuffer('Dart is fun');
  for (var i = 0; i < 5; i++) {
    message.write('!');
  }

  print('message = ${message.toString()}');
  //message = Dart is fun!!!!!

  print("------------------------------------------------------------------");

  //在 Dart 语言中，for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱。假设有如下代码：
  var callbacks = [];
  for (var k = 0; k < 2; k++) {
    callbacks.add(() => print(k));
  }

  print("------------------------------------------------------------------");

  for (final c in callbacks) {
    c();
  }

  //如果要遍历的对象是一个可迭代对象（例如 List 或 Set），并且你不需要知道当前的遍历索引，则可以使用 for-in 方法进行 遍历：

  var fixedNum = [1, 2, 3];
  for (final n in fixedNum) {
    print('n = $n');
  }

  print("------------------------------------------------------------------");

  /*
      Switch 和 Case
      Switch 语句在 Dart 中使用 == 来比较整数、字符串或编译时常量，比较的两个对象必须是同一个类型且不能是子类并且没有重写 == 操作符。 枚举类型非常适合在 Switch 语句中使用。

   */
  var command = 'OPEN';

  switch (command) {
    case 'CLOSED':
      executeClosed();
      break;
    default:
      executeUnknown();
  }

  /*
      捕获异常
      捕获异常可以避免异常继续传递（重新抛出异常除外）。捕获一个异常可以给你处理它的机会：

      关键字 rethrow 可以将捕获的异常再次抛出：


       Finally
       无论是否抛出异常，finally 语句始终执行，如果没有指定 catch 语句来捕获异常，则异常会在执行完 finally 语句后抛出：

   */

  try {} on FormatException {
  } on Exception catch (e) {
    rethrow; //关键字 rethrow 可以将捕获的异常再次抛出：
  } finally {}

  var p = Point(2, 3);
  var p2 = new Point(2, 3);
  // var p3 = Point.fromJson({'x': 1, 'y': 2});

  var p21 = Point2();
  p21.x = 4;
  print("------------------------------------------------------------------");

  var profileMark = ProfileMark("测试名字");
  print(profileMark.name + " ， 时间： ${profileMark.start.toString()}");
  //测试名字 ， 时间： 2023-04-28 08:05:48.539494

  var p31 = Point3(31, 21);
  print('p31 = ${p31.x} , ${p31.y}');
  var p32 = Point3.origin();
  print('p32 = ${p32.x} , ${p32.y}');
  //p31 = 31.0 , 21.0
  // p32 = 61.0 , 51.0

  print("------------------------------------------------------------------");

  var employee2 = Employee2.fromJson({});
  print(employee2);
  //in Person2
  // in Employee2
  // Instance of 'Employee2'

  print("------------------------------------------------------------------");

  var point4 = Point4.fromJson({'x': 55, 'y': 66});
  print('point4.x = ${point4.x} , point4.y = ${point4.y}');
  //point4.x = 55.0 , point4.y = 66.0

  print("------------------------------------------------------------------");
  var point5 = Point5(3, 4);
  print(point5.distanceFromOrigin);
  //distanceFromOrigin = 5.0
}

void executeClosed() {}

void executeUnknown() {}

String playerName(String? parmName) => parmName ?? 'Guest';

/*
  级联运算符
  级联运算符 (.., ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法。

  https://www.jianshu.com/p/592539ac8ef1


  注意：
   ?.. 运行在 2.12 和以上的 版本 中可用。
 */
class Model {
  String hello(String name) {
    var value = 'hello $name';
    print(value);
    return value;
  }

  void sayHi(String name) {
    print("hi $name");
  }

  void sayGoodbye(String name) {
    print('goodbye $name');
  }
}

/*
  Dart构造函数
  https://www.jianshu.com/p/e59052e49bf3/

  构造函数
声明一个与类名一样的函数即可声明一个构造函数（对于命名式构造函数 还可以添加额外的标识符）。大部分的构造函数形式是生成式构造函数，其用于创建一个类的实例：

使用 this 关键字引用当前实例。
当且仅当命名冲突时使用 this 关键字才有意义，否则 Dart 会忽略 this 关键字。


终值初始化
对于大多数编程语言来说在构造函数中为实例变量赋值的过程都是类似的，而 Dart 则提供了一种特殊的语法糖来简化该步骤。

构造中初始化的参数可以用于初始化非空或 final 修饰的变量，它们都必须被初始化或提供一个默认值。
Point(this.x, this.y);

 */
class Point {
  late int x;
  late int y;

  // Point(this.x, this.y)

  Point(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

/*
  所有未初始化的实例变量其值均为 null。

 所有实例变量均会隐式地声明一个 Getter 方法。非终值的实例变量和 late final 声明但未声明初始化的实例变量还会隐式地声明一个 Setter 方法

 如果你在实例中声明了没有 late 修饰的变量，它会在实例初始化时早于构造方法进行赋值。
 因此，没有使用 late修饰的变量无法访问到 this
 */
class Point2 {
  double? x;
  double? y;
  double z = 0;
}

/*
  实例变量可以是final，在这种情况下，它们必须只设置一次。在声明时，使用构造函数形参或构造函数的初始化列表初始化最终的、非延迟的实例变量:
 */
class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);

  ProfileMark.unnamed() : name = '';
}

/*
  命名式构造函数
  可以为一个类声明多个命名式构造函数来表达更明确的意图：


  记住构造函数是不能被继承的，这将意味着子类不能继承父类的命名式构造函数，如果你想在子类中提供一个与父类命名构造函数名字一样的命名构造函数，则需要在子类中显式地声明。

 */

const double xOrigin = 61;
const double yOrigin = 51;

class Point3 {
  final double x;
  final double y;

  Point3(this.x, this.y);

  // Named constructor
  Point3.origin()
      : x = xOrigin,
        y = yOrigin;
}

/*

  调用父类非默认构造函数
默认情况下，子类的构造函数会调用父类的匿名无参数构造方法，并且该调用会在子类构造函数的函数体代码执行前，如果子类构造函数还有一个 初始化列表，那么该初始化列表会在调用父类的该构造函数之前被执行，总的来说，这三者的调用顺序如下：

初始化列表

父类的无参数构造函数

当前类的构造函数

如果父类没有匿名无参数构造函数，那么子类必须调用父类的其中一个构造函数，为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用（:）指定。

因为参数会在子类构造函数被执行前传递给父类的构造函数，因此该参数也可以是一个表达式，比如一个函数：

  注意：

  传递给父类构造函数的参数不能使用 this 关键字，因为在参数传递的这一步骤，子类构造函数尚未执行，子类的实例对象也就还未初始化，因此所有的实例成员都不能被访问，但是类成员可以。


 */
//下面的示例中，Employee 类的构造函数调用了父类 Person 的命名构造函数
class Person2 {
  String? firstName;

  Person2.fromJson(Map data) {
    print('in Person2');
  }
}

class Employee2 extends Person2 {
  Employee2.fromJson(super.data) : super.fromJson() {
    print('in Employee2');
  }
}

/*
  超类参数
  为了不重复地将参数传递到超类构造的指定参数，你可以使用超类参数，直接在子类的构造中使用超类构造的某个参数。超类参数不能和重定向的参数一起使用。超类参数的表达式和写法与 终值初始化 类似：


  使用超类参数需要 Dart SDK 版本 至少为 2.17。在先前的版本中，你必须手动传递所有的超类构造参数。
 */

class Vector2d {
  final double x;
  final double y;

  Vector2d(this.x, this.y);

  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  final double z;

  Vector3d(super.x, super.y, this.z);

  //如果超类构造的位置参数已被使用，那么超类构造参数就不能再继续使用被占用的位置。但是超类构造参数可以始终是命名参数：
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
// Vector3d.yzPlane({required super.x, required this.z}) : super.named(y: 0);
}

/*
  初始化列表
 除了调用父类构造函数之外，还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。

 初始化列表表达式 = 右边的语句不能使用 this 关键字。
 */

class Point4 {
  final double x;
  final double y;

  Point4.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']! {
    print('In Point4.fromJson(): ($x, $y)');
  }
}

//使用初始化列表设置 final 字段非常方便，下面的示例中就使用初始化列表来设置了三个 final 变量的值

class Point5 {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point5(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

/*
  重定向构造函数
  有时候类中的构造函数仅用于调用类中其它的构造函数，此时该构造函数没有函数体，只需在函数签名后使用（:）指定需要重定向到的其它构造函数 (使用 this 而非类名)：

 */

class Point6 {
  double x, y;

  Point6(this.x, this.y);

  Point6.alongXAxis(double x) : this(x, 0);
}

//常量构造函数
// 如果类生成的对象都是不变的，可以在生成这些对象时就将其变为编译时常量。你可以在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现该功能。
//常量构造函数创建的实例并不总是常量
class ImmutablePoint {
  final double x, y;

  const ImmutablePoint(this.x, this.y);

  static const ImmutablePoint immutablePoint = ImmutablePoint(0, 0);
}

//工厂构造函数
//使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数，这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。
// 例如，工厂构造函数可能会从缓存中返回一个实例，或者返回一个子类型的实例。

/*
  在如下的示例中， Logger 的工厂构造函数从缓存中返回对象，和 Logger.fromJson 工厂构造函数从 JSON 对象中初始化一个最终变量。

  在工厂构造函数中无法访问 this
 */

class Logger {
  final String name;
  bool mute = false;

  Logger._internal(this.name);

  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  void log(String msg) {
    if (!mute) print(msg);
  }
}

abstract class AbstractContainer {
  void updateChildren();
}

/*
  枚举类型
枚举类型是一种特殊的类型，也称为 enumerations 或 enums，用于定义一些固定数量的常量值。

 备注:

所有的枚举都继承于 Enum 类。枚举类是封闭的，即不能被继承、被实现、被 mixin 混入或显式被实例化。

抽象类和 mixin 可以显式的实现或继承 Enum，但只有枚举可以实现或混入这个类，其他类无法享有同样的操作。

 */
enum Color { red, green, blue }


/*
  使用 Mixin 为类添加功能
  Mixin 是一种在多重继承中复用某个类中代码的方法模式。

  使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式：

  mixin 关键字在 Dart 2.1 中才被引用支持。早期版本中的代码通常使用 abstract class 代替

 */

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe(){
    if(canPlayPiano){
      print('Playing piano');
    }else if(canConduct){
      print('Waving hands');
    }else{
      print('Humming to self');
    }
  }
}

class Performer{

}

//可以使用关键字 on 来指定哪些类可以使用该 Mixin 类

class Musician {

}

mixin MusicalPerformer on Musician{

}

class SingerDancer extends Musician with MusicalPerformer{

}